//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Reflection;
using System.Text;
using OpenLS.Spreadsheet.Expressions;

namespace OpenLS.Spreadsheet.Formats.Biff.Tokens
{
#if DEBUG
    [ContractClass(typeof(FormulaTokenContract))]
#endif
    internal abstract class FormulaToken
    {
#if DEBUG
        public sealed override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(GetType().Name);
            stringBuilder.Append("{");
            IEnumerable<FieldInfo> fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.GetField);
            var strings = new List<string>( Enumerable.Select(fields, f => f.Name.Replace(">k__BackingField", "") + " = " + NiceValue(f.GetValue(this))));
            stringBuilder.Append(string.Join(", ", strings.ToArray()));
            stringBuilder.Append("}");
            return stringBuilder.ToString();
        }
        static string NiceValue(object o)
        {
            if (o == null)
                return "null";
            if (o is string)
                return o.ToString();
            if (o is IEnumerable)
            {
                List<string> ss = new List<string>();
                foreach (var oo in ((IEnumerable)o))
                    ss.Add(NiceValue(oo));
                return string.Format("{{{0}}}", string.Join(",", ss.ToArray()));
            }
            return o.ToString();
        }

        byte [] GetReadContentBytes(FormulaReadContext context)
        {
            var oldPosition = context.BaseStream.Position;
            ReadContent(context);
            var newPosition = context.BaseStream.Position;
            context.BaseStream.Position = oldPosition;
            var length = newPosition - oldPosition;
            byte [] result = new byte[length];
            context.BaseStream.Read(result, 0, result.Length);
            context.BaseStream.Position = oldPosition;
            return result;
        }
        byte[] GetWriteContentBytes(FormulaReadContextType type)
        {
            MemoryStream memoryStream = new MemoryStream();
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
            FormulaWriteContext context2 = new FormulaWriteContext(binaryWriter, type);
            this.WriteContent(context2);
            binaryWriter.Flush();
            var newPosition = memoryStream.Position;
            memoryStream.Position = 0;
            var result = new byte[newPosition];
            memoryStream.Read(result, 0, result.Length);
            return result;
        }
        public void AssertReadWrite (FormulaReadContext context)
        {
            byte[] readBytes = GetReadContentBytes(context);
            byte[] writeBytes = GetWriteContentBytes(context.Type);
            Contract.Assert(readBytes.Length == writeBytes.Length);
            for (int i =0 ; i < readBytes.Length; i++)
            {
                Contract.Assert(readBytes[i] == writeBytes[i]);
            }
        }
#endif
        public abstract ushort GetLength(FormulaReadContextType formulaReadContextType);
        internal abstract void ReadContent(FormulaReadContext context);

        internal abstract Expression GetExpression(FormulaEvaluationContext context);
        public abstract void WriteContent(FormulaWriteContext context);
        public void Write(FormulaWriteContext writer)
        {
            writer.Writer.Write((byte)this.TokenId);
            WriteContent((writer));
        }

        public abstract FormulaTokenId TokenId { get; }
    }
#if DEBUG
    [ContractClassFor(typeof(FormulaToken))]
    internal abstract class FormulaTokenContract : FormulaToken
    {
        internal override void ReadContent(FormulaReadContext context)
        {
            Contract.Ensures(context.BaseStream.Position == Contract.OldValue(context.BaseStream.Position) + GetLength(context.Type));
        }

        public override void WriteContent(FormulaWriteContext context)
        {
            Contract.Ensures(context.Writer.BaseStream.Position == Contract.OldValue(context.Writer.BaseStream.Position) + GetLength(context.Type));
        }

        internal override Expression GetExpression(FormulaEvaluationContext context)
        {
            Contract.Requires(context != null);
            return default(Expression);
        }
    }
#endif
}